//#include "jjwf/xutility/code_convert.hpp"
#include "code_convert.hpp"

#include <locale>

#if defined(_MSC_VER)
#if _MSC_VER>=1600
#define HAVE_STD_CODECVT 1
#endif
#elif defined(__linux__)
#if GCC_VERSION>=50300
#define HAVE_STD_CODECVT 1
#endif
#elif defined(__APPLE__)
#define HAVE_STD_CODECVT 1
#endif

#undef HAVE_STD_CODECVT // for test

#ifdef HAVE_STD_CODECVT
#include <codecvt>
#endif

namespace jjwf {
    
    namespace codecvt {
        
#if defined(_WIN32)
        const char* kChineseLocale = "chs";
#elif defined(__APPLE__)
        const char* kChineseLocale = "zh_CN.GBK";
#else
        const char* kChineseLocale = "zh_CN.gb18030";
#endif
        
        std::wstring ansi_to_utf16(const std::string& ansi, const char* locale)
        {
            std::wstring out;
            size_t u16_capacity = ansi.size() * 2 + 2;
            out.resize(u16_capacity);
            
            setlocale(LC_ALL, locale);
            size_t ucs_len = std::mbstowcs((wchar_t*)out.c_str(), ansi.c_str(), u16_capacity);
            
            if (ucs_len == size_t(-1))
                out.resize(0);
            else
                out.resize(ucs_len);
            
            return std::move(out);
        }
        
        std::string  utf16_to_ansi(const std::wstring& utf16, const char* locale)
        {
            std::string out;
            size_t ansi_capacity = utf16.size() * 2 + 1;
            out.resize(ansi_capacity);
            
            ::setlocale(LC_ALL, locale);
            size_t ansi_len = std::wcstombs((char*)out.c_str(), utf16.c_str(), ansi_capacity);
            
            if (ansi_len == size_t(-1))
                out.resize(0);
            else
                out.resize(ansi_len);
            
            return std::move(out);
        }
        
        std::wstring utf8_to_utf16(const std::string& utf8)
        {
            std::wstring out;
            
            char* temp_utf8 = (char*)utf8.c_str();
            int count = (int)utf8.size();
            
            wchar_t wch(0);
            
            while (count > 0)
            {
                if ((*temp_utf8 & 0x80) == 0x00)        // 1byte
                {
                    wch = *temp_utf8;
                    temp_utf8++;
                    count--;
                }
                else if ((*temp_utf8 & 0xe0) == 0xc0 &&
                         (*(temp_utf8 + 1) & 0xc0) == 0x80) // 2byte
                {
                    wch = ((unsigned short)(*temp_utf8 & 0x1f) << 6)
                    | (unsigned short)(*(temp_utf8 + 1) & 0x3f);
                    temp_utf8 += 2;
                    count -= 2;
                }
                else if ((*temp_utf8 & 0xf0) == 0xe0 &&
                         (*(temp_utf8 + 1) & 0xc0) == 0x80 &&
                         (*(temp_utf8 + 2) & 0xc0) == 0x80) // 3byte
                {
                    wch = ((unsigned short)(*temp_utf8 & 0x0f) << 12)
                    | ((unsigned short)(*(temp_utf8 + 1) & 0x3f) << 6)
                    | (unsigned short)(*(temp_utf8 + 2) & 0x3f);
                    temp_utf8 += 3;
                    count -= 3;
                }
                else
                {
                    out.resize(0);
                    break;                            // invalid character
                }
                
                out.append(1, wch);
            }
            
            return std::move(out);
        }
        
        std::string  utf16_to_utf8(const std::wstring& utf16)
        {
            std::string out;
            
            int count = (int)utf16.size();
            wchar_t* temp_ucs = (wchar_t*)utf16.c_str();
            char ch1, ch2, ch3;
            
            while (count > 0)
            {
                if (*temp_ucs < 0x0080)
                {
                    // ASCII  1 byte
                    ch1 = (unsigned char) * temp_ucs;
                    out.append(1, ch1);
                }
                else if (*temp_ucs < 0x0800)
                {
                    ch1 = 0xc0 | ((unsigned char)(*temp_ucs >> 6));
                    ch2 = 0x80 | ((unsigned char)(*temp_ucs & 0x003f));
                    
                    out.append(1, ch1);
                    out.append(1, ch2);
                }
                else
                {
                    ch1 = 0xe0 | ((unsigned char)(*temp_ucs >> 12));
                    ch2 = 0x80 | ((unsigned char)((*temp_ucs & 0x0fc0) >> 6));
                    ch3 = 0x80 | ((unsigned char)(*temp_ucs & 0x003f));
                    out.append(1, ch1);
                    out.append(1, ch2);
                    out.append(1, ch3);
                }
                
                ++temp_ucs;
                count--;
            }
            
            return std::move(out);
        }
        
        std::string utf8_to_ansi(const std::string& utf8, const char* locale)
        {
            std::wstring utf16 = std::move(utf8_to_utf16(utf8));
            std::string ansi = std::move(utf16_to_ansi(utf16, locale));
            return std::move(ansi);
        }
        
        std::string ansi_to_utf8(const std::string& ansi, const char* locale)
        {
            std::wstring utf16 = std::move(ansi_to_utf16(ansi, locale));
            std::string utf8 = std::move(utf16_to_utf8(utf16));
            return std::move(utf8);
        }
        
        std::string utf8_to_gbk(const std::string& utf8)
        {
            return std::move(
                             utf8_to_ansi(utf8, kChineseLocale)
                             );
        }
        
        std::string gbk_to_utf8(const std::string& gbk)
        {
            return std::move(
                             ansi_to_utf8(gbk, kChineseLocale)
                             );
        }
        
        std::wstring gbk_to_utf16(const std::string& gbk)
        {
            return std::move(ansi_to_utf16(gbk, kChineseLocale));
        }
        
        std::string  utf16_to_gbk(const std::wstring& utf16)
        {
            return std::move(utf16_to_ansi(utf16, kChineseLocale));
        }
        
    } // end of namespace codecvt
} // end of namespace jjwf

